性能优化一直都是一个 Android 开发者逃不过的话题,启动优化则更是重中之重。
启动速度可以直接影响一个 App 的留存率和转化率,没有人会希望自己点击之后还要等一会才打开。
但是当我一番调研后发现,网上大部分启动优化相关的文章,套路都差不多,我称之为老三样。
这么做的目的主要是为了消除启动时的黑白屏,给用户一种秒响应的感觉,但是并不会真正减少用户启动时间,仅属于视觉优化。
2)用 ViewStub 替代在启动过程中不需要显示的 UI 控件
3. Application 和 主 Activity 的 onCreate 中异步初始化某些代码
因为在主线程上进行资源初始化会降低启动速度,所以可以将不必要的资源初始化延迟,达到优化的效果。但是这里要注意懒加载集中化的问题,别用户启动时间快了,但是无法在界面上操作就尴尬了。
老三样并不说是不管用或者过时了,只是这三种优化方式都是非常基础的方式,当你的启动优化遇到了瓶颈,是不能够再通过这三种方式突破的。
而且只会基础的优化方式,并不会在履历中展现出优势。
所以今天说说在老三样的基础优化之上还有哪些可行的方案。
python $ANDROID_HOME/platform-tools/systrace/systrace.py gfx view wm am pm ss dalvik app sched -b 90960 -a 你的包名 -o test.log.html
这一步需要你系统环境配置了 ANDROID_HOME 环境变量。
3)运行你的App,正常操作到你想测性能的地方,然后再命令行窗口中按 Enter 键停止收集4)用 chrome(只支持此浏览器)打开生成的 test.log.html 结果文件,打开效果如下图:
目前需要关心的地方就是我们的应用进程相关的,也就是红框圈起来的地方。图中的 F 代表绘制帧,黄色/红色表示该帧绘制超时,绿色代表绘制正常,也就是在16.6ms内绘制完一帧。
所以可以很容易发现哪些启动过程中没有用到的 UI 控件也被渲染了,这时就可以用 ViewStub 去替代。但是现在可以看到的都是系统调用的耗时情况,因为谷歌预先在代码里关键的地方加上了监控,如果想要看到自己方法的耗时,那需要手动在方法入口加上 Trace.beginSection("TAG")在方法结束的地方加上 Trace.endSection()这样就可以在生成的结果中看到我们自定义的 tag。如果很多地方你都想加上监控,手动加是肯定不合适的,这里推荐函数插桩方式自动加上监控代码,参考 systrace+函数插桩
这种方式不仅可以帮助监控启动过程中性能问题,再做卡顿优化的时候也可以用这种方式。定位到了耗时方法,再做一些针对性的优化就相对容易了。http://gityuan.com/2016/01/17/systrace/
redex 是 Facebook 开源的一款字节码优化工具,目前只支持 mac 和 linux。我们用的是里面的 interdex 功能来重排列我们 dex 中的 class 文件,那么为什么重排列 class 文件可以优化启动速度?简单的说,通过文件重排列的目的,就是将启动阶段需要用到的文件在 APK 文件中排布在一起,尽可能的利用 Linux 文件系统的 pagecache 机制,用最少的磁盘 IO 次数,读取尽可能多的启动阶段需要的文件,减少 IO 开销,从而达到提升启动性能的目的。
1)安装配置 redex
2)获得启动过程中 class 文件的加载顺序
3)根据这个顺序重排列 dex 中的 class 文件git clone https://github.com/facebook/redex.git
xcode-select --install
brew install autoconf automake libtool python3
brew install boost jsoncpp
cd redex
autoreconf -ivf && ./configure && make
sudo make install
编译时间较久,不想干等着,可以加上 say 指令,编译完成后语音通知autoreconf -ivf && ./configure && make && say '编译完成'
因为 redex 默认不开启 interdex,所以我们要在配置文件中加上相应的配置,在 redex 文档中有说明cd redex/config/
vi default.config
这里按照 redex 提供的工具获取,但是需要手机有 root 权限adb shell ps | grep 你的应用包名
adb root
adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof
adb pull /data/local/tmp/SOMEDUMP.hprof YOUR_DIR_HERE/
通过 python 脚本解析堆内存,生成类加载顺序列表python redex/tools/hprof/dump_classes_from_hprof.py --hprof YOUR_DIR_HERE/SOMEDUMP.hprof > list_of_classes.txt
ps: 这个脚本支持 Python 2,执行过程中如果遇到某个库没安装之类的,直接通过 pip install 缺失的库 就可以。ANDROID_SDK=你的Android sdk路径 redex input.apk -o output.apk
这时候生成的 output.apk 是不能直接安装的,需要重新签名,我测试用的是 debug 包,所以重新签了debug 的签名jarsigner -keystore ~/.android/debug.keystore -storepass android -keypass android output.apk androiddebugkey
到这就可以重新安装测试了,按照 facebook 的说法和一些大厂的实践,启动速度大概可以提高 10%~20%,在低端机型上效果应该更明显。
关于 redex 的使用和相关配置文档,都可以在 redex/docs/ 目录下查看。
启动耗时测量
为了正确诊断冷启动的性能,需要冷启动的时间指标,下面有两种简单的方式:adb命令 : adb shell am start -S -W 包名/启动类的全名adb shell am start -S -W com.android.helloword/com.android.helloword.MainActivity
ThisTime : 最后一个 Activity 的启动耗时TotalTime : 启动一连串的 Activity 总耗时WaitTime : 应用进程的创建过程 + TotalTime谷歌在 Android4.4(API 19)上也提供了测量方法,在 logcat 中过滤 Displayed 字段,
输出的值表示在启动过程和完成在屏幕上绘制相应 Activity 之间经过的时间,其实和上面的方式得到的结果是一样的。关于 Android App 的冷启动过程和一些概念可以参考谷歌官方文档 「App startup time 」https://developer.android.com/topic/performance/vitals/launch-time
由于一些原因,还有一些优化方法没有实践,有兴趣的可以自行了解:1)启动过程中的 GC 优化,尽量减少 GC 次数,避免大量或者频繁创建对象,如必须,可尝试放到 Native 实现2)线程优化,尽可能减少 cpu 调度,具体就是控制线程数量和调度3)在类加载的过程中通过 Hook 去掉类验证的过程,可以在 systrace 生成的文件中看到 verifyClass 过程,因为需要校验方法的每一个指令,所以是一个比较耗时的操作。以上就是我关于 Android 冷启动优化的一些总结,水平有限,难免出现不准确的地方,欢迎指正。
JakeWharton评价我的代码像是在打地鼠?
换掉Retrofit+RxJava, 开启 LiveData+Retrofit之路
不破不立!
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!